package com.lemon.logging.aspect;

import com.google.common.base.Joiner;
import com.google.common.base.Stopwatch;
import com.google.gson.Gson;
import com.lemon.constant.ConfigSwitchConstant;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author lemon
 * @version 1.0
 * @description: 基于 @RequestMapping 注解输出请求入参、响应结果
 * @date Create by lemon on 2020-07-02 17:37
 */
@Slf4j
@Aspect
@Order(1)
@Component
@ConditionalOnProperty(name = ConfigSwitchConstant.LOGGER_ASPECT_ENABLE, havingValue = ConfigSwitchConstant.LOGGER_ASPECT_ENABLE_DEFAULT, matchIfMissing = true)
public class RequestAndResponseLoggerAspect {
    @Value("#{'${log.ignore.methodArgs:}'.toLowerCase().split(',')}")
    private List<String> ignoreMethodArgs;

    /**
     * @param
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-02 17:43
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void requestMapping() {
    }

    /**
     * @param
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-02 17:43
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getMapping() {
    }

    /**
     * @param
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-02 17:43
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void postMapping() {
    }

    /**
     * @param
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-02 17:43
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
    public void putMapping() {
    }

    /**
     * @param
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-02 17:43
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PatchMapping)")
    public void patchMapping() {
    }

    /**
     * @param
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-02 17:43
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public void deleteMapping() {
    }

    /**
     * @param
     * @return void
     * @description
     * @author lemon
     * @date 2020-07-02 17:43
     */
    @Pointcut("requestMapping() || getMapping() || postMapping() || putMapping() || patchMapping() || deleteMapping()")
    public void webLog() {
    }

    /**
     * @param proceedingJoinPoint
     * @return java.lang.Object
     * @description
     * @author lemon
     * @date 2020-07-02 17:43
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        Stopwatch stopWatch = Stopwatch.createStarted();

        try {
            result = proceedingJoinPoint.proceed();
            return result;
        } finally {
            long timeConsuming = stopWatch.elapsed(TimeUnit.MILLISECONDS);
            Gson gson = new Gson();
            // 构造参数组集合
            List<Object> argList = new ArrayList<>();

            for (Object arg : proceedingJoinPoint.getArgs()) {
                // request/response无法使用toJSON
                if (this.ignoreMethodArg(arg)) {
                    continue;
                } else {
                    argList.add(gson.toJson(arg));
                }
            }

            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();

            log.info("Process time: [{}]ms, Request uri: [{}], HTTP method: [{}], Request args: [{}], Response result: {}",
                    timeConsuming, request.getRequestURI(), request.getMethod(), Joiner.on(";").join(argList), gson.toJson(result));
        }
    }

    /**
     * @param arg
     * @return boolean
     * @description 忽略参数
     * @author lemon
     * @date 2020-07-02 19:26
     */
    protected boolean ignoreMethodArg(Object arg) {
        if (arg == null) {
            return false;
        }

        if (arg instanceof ServletRequest || arg instanceof ServletResponse
                || arg instanceof HttpServletRequest || arg instanceof HttpServletResponse
                || arg instanceof Errors || ignoreMethodArgs.contains(arg.getClass().getName().toLowerCase())) {
            return true;
        }

        return false;
    }
}
